# Rename bulkloader progress/result db and its corresponding internal keying.
import sqlite3
from sys import stderr
from pdb import runcall

SIGNATURE_TABLE_NAME = None
def get_signature_table_name():
    global SIGNATURE_TABLE_NAME
    try: from google.appengine.tools.bulkloader import SIGNATURE_TABLE_NAME
    except ImportError:
        SIGNATURE_TABLE_NAME = 'bulkloader_database_signature'

    return SIGNATURE_TABLE_NAME

def parse_signature(sigstring):
    data = {}
    for line in sigstring.split('\n'):
        line = line.strip()
        colon = line.find(':')
        if colon >= 0:
            name = line[:colon].strip()
            value = line[colon+1:].strip()
            if name:
                data[str(name)] = str(value)

    return data

def read_signature(conn):
    u = conn.cursor()
    u.execute('select value from %s limit 1' % get_signature_table_name())

    return u.fetchone()[0]

def change_signature(conn, signature):
    old_sig = read_signature(conn)
    conn.execute('delete from %s where value = ?' % get_signature_table_name(),
                 (old_sig,))

    conn.execute('insert into %s (value) values (?)' % get_signature_table_name(),
                 (signature,))

    conn.commit()

def load_signature(conn):
    sigstring = read_signature(conn)
    if sigstring:
        return parse_signature(sigstring)

from google.appengine.tools.bulkloader import _MakeSignature

# How does result_db_line acquire 'result_db: ' value if download is rendered as False?!?
##    def _MakeSignature(app_id=None,
##                       url=None,
##                       kind=None,
##                       db_filename=None,
##                       perform_map=None,
##                       download=None,
##                       has_header=None,
##                       result_db_filename=None,
##                       dump=None,
##                       restore=None):
##      """Returns a string that identifies the important options for the database."""
##      if download:
##        result_db_line = 'result_db: %s' % result_db_filename
##      else:
##        result_db_line = ''
##      return u"""
##      app_id: %s
##      url: %s
##      kind: %s
##      download: %s
##      map: %s
##      dump: %s
##      restore: %s
##      progress_db: %s
##      has_header: %s
##      %s
##      """ % (app_id, url, kind, download, perform_map, dump, restore, db_filename,
##             has_header, result_db_line)

def build_signature(sig):
    # result_db_line = 'result_db: %s' % sig['result_db_filename']
    # result_db_line = sig.get('result_db', '')
    result_db_line = ''

    sigstring = _MakeSignature(app_id = sig['app_id'],
                               url = sig['url'],
                               kind = sig['kind'],
                               db_filename = sig['progress_db'],
                               perform_map = sig['map'],
                               download = sig['download'],
                               has_header = sig['has_header'],
                               result_db_filename = result_db_line,
                               dump = sig['dump'],
                               restore = sig['restore'])

    # XXX HACK XXX
    sigstring = sigstring.replace('result_db: ', '')

    # !! Actually, the 
     # XXX Why does error show it in one order and the ResumeError in another?
     # bulkloader.py:1656

    return sigstring

_sqlite_master_table_name = 'SQLite_Master'
_sqlite_master_table_name = 'sqlite_master'

def detect_db_file_type(conn):
    u = conn.cursor()
    u.execute('select name from %s' % _sqlite_master_table_name)

    for table_name in u.fetchall():
        table_name = table_name[0]

        if table_name in ('progress', 'result'):
            return str(table_name)

def replace_db_name_kind(string, kind = '', db_file_type = ''):
    # todo: normpath?
    return str(string.replace('$KIND', kind).replace('$DB_FILE_TYPE', db_file_type))

from os import rename as rename_file
from sys import exc_info
from errno import ENOENT, EEXIST
from os.path import dirname
from os import makedirs

def ensureDirectoryExists(path):
    try: makedirs(path)
    except OSError, e:
        if e.args[0] != EEXIST:
            (etype, value, tb) = exc_info()
            raise etype, value, tb
    else:
        print 'Created %r' % path

def doRenameFile(old_filename, new_filename):
    ensureDirectoryExists(dirname(new_filename))

    try: rename_file(old_filename, new_filename)
    except OSError, e:
        if e.args[0] == ENOENT:
            return False

        (etype, value, tb) = exc_info()
        raise etype, value, tb
    else:
        print 'Moved %r into %r' % (old_filename, new_filename)

    return True

class DbFile:
    def __init__(self, filename, conn = None):
        self.filename = filename
        self.conn = conn

        self.open_database()
        conn = self.conn

        self.db_file_type = detect_db_file_type(conn)
        self.signature = load_signature(conn)

    def close_database(self):
        if self.conn is not None:
            self.conn.close()
            self.conn = None

    def open_database(self):
        if self.conn is None:
            self.conn = sqlite3.connect(self.filename)

    def build_signature(self):
        return build_signature(self.signature)

    def get_signature_value(self, name):
        return str(self.signature.get(name))
    def __getitem__(self, name):
        return self.get_signature_value(name)

    def get_kind(self):
        return self['kind']
    def get_progress_db(self):
        return self['progress_db']

    def change_progress_db(self, new_filename, interpolate = False, rename = False):
        if interpolate:
            new_filename = replace_db_name_kind(new_filename, self.get_kind(),
                                                self.db_file_type)

            if new_filename == self.get_progress_db():
                return

        self.signature['progress_db'] = str(new_filename)
        change_signature(self.conn, self.build_signature())

        if rename:
            self.close_database()
            try:
                if doRenameFile(self.filename, new_filename):
                    self.filename = new_filename
            finally:
                self.open_database()

    def inspection(self):
        return '\n'.join([self.filename, self.db_file_type, '---',
                          self.build_signature()])

def get_db_filelist(options, args):
    if options.db_file:
        yield options.db_file

    for dbfile in args:
        yield dbfile

def process_db_file(dbfile, options):
    db = DbFile(dbfile)

    new_db_name = options.new_db_name_template
    if new_db_name:
        print 'Changing: %r ...' % db.filename
        db.change_progress_db(new_db_name, interpolate = True,
                              rename = options.rename_db_file)

    if options.examine:
        from my.interactive import examine
        examine(db = db, globals = globals())

    if options.inspect:
        print db.inspection()
        if new_db_name:
            print 'new-db-name-template: %r' % new_db_name

    if options.simple_inspection:
        print '%s: %s (%s)' % (db.filename, db.db_file_type, db.get_progress_db())

from traceback import print_exc as full_traceback
from sys import exc_info

def simple_error_display():
    (etype, value, tb) = exc_info()
    print '%s: %s' % (str(etype), str(value))

def main(argv = None):
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option('--db-file')
    parser.add_option('--new-db-name-template', '--new-db-name')
    parser.add_option('-M', '--rename-db-file', action = 'store_true')

    parser.add_option('-e', '--examine', action = 'store_true')
    parser.add_option('-i', '--inspect', action = 'store_true')

    parser.add_option('--simple-inspection', action = 'store_true')
    parser.add_option('--full-traceback', action = 'store_true')

    (options, args) = parser.parse_args(argv)
    nr = 0
    for dbfile in get_db_filelist(options, args):
        nr += 1

        try: process_db_file(dbfile, options)
        except:
            if options.full_traceback:
                full_traceback()
            else:
                simple_error_display()

    if not nr:
        parser.print_usage(stderr)

# Determine tables:
#
#  CREATE TABLE bulkloader_database_signature (value TEXT not null);
#
#    app_id: cbanis
#    url: http://localhost:8080/gae/remote_api
#    kind: MobileSpecialSetting
#    download: False
#    map: False
#    dump: False
#    restore: False
#    progress_db: bulkloader-progress-20100628.212108.sql3
#    has_header: False
#
#  CREATE TABLE progress (id integer primary key autoincrement,        
#                         state integer not null,
#                         kind text not null,
#                         key_start INTEGER,
#                         key_end INTEGER);
#
#  CREATE TABLE result (id BLOB primary key,
#                       value BLOB not null,
#                       sort_key BLOB);

# Generally the filename will describe the table, but in this case,
# use db introspection somehow to check for progress or result tables.

# Parse signature data to glean the entity kind name for rebuilding the
# workfile in its qualified directory structure.

# options.new_db_name_template

if __name__ == '__main__':
    main()
